package com.dingmouren.paletteimageview;

import com.dingmouren.paletteimageview.factory.PixelMapFactory;
import com.dingmouren.paletteimageview.util.ColorUtil;
import com.dingmouren.paletteimageview.util.JsonUtil;
import com.dingmouren.paletteimageview.util.LogUtil;
import ohos.agp.utils.Rect;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.PixelMap;

import java.util.*;

public class Palette {

    /**
     * Listener to be used with {@link #generateAsync(PixelMap, PaletteAsyncListener)} or
     * {@link #generateAsync(PixelMap, int, PaletteAsyncListener)}
     */
    public interface PaletteAsyncListener {

        /**
         * Called when the {@link Palette} has been generated. {@code null} will be passed when an
         * error occurred during generation.
         */
        void onGenerated(Palette palette);
    }



    static final int DEFAULT_RESIZE_PixelMap_AREA = 112 * 112;
    static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;

    static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
    static final float MIN_CONTRAST_BODY_TEXT = 4.5f;

    static int COLOR_BLACK = 0xFF000000;

    static int COLOR_WHITE = 0xFFFFFFFF;

    /**
     * Start generating a {@link Palette} with the returned {@link Builder} instance.
     */
    
    public static Builder from(PixelMap PixelMap) {
        return new Builder(PixelMap);
    }

    /**
     * Generate a {@link Palette} from the pre-generated list of {@link Swatch} swatches.
     * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
     * list of swatches. Will return null if the {@code swatches} is null.
     */
    
    public static Palette from(List<Swatch> swatches) {
        return new Builder(swatches).generate((EventHandler)null);
    }

    /**
     * @deprecated Use {@link Builder} to generate the Palette.
     */
    @Deprecated
    public static Palette generate(PixelMap PixelMap) {
        return from(PixelMap).generate((EventHandler)null);
    }

    /**
     * @deprecated Use {@link Builder} to generate the Palette.
     */
    @Deprecated
    public static Palette generate(PixelMap PixelMap, int numColors) {
        return from(PixelMap).maximumColorCount(numColors).generate((EventHandler)null);
    }

    /**
     * @deprecated Use {@link Builder} to generate the Palette.
     */
    @Deprecated
    public static EventRunner generateAsync(PixelMap PixelMap, PaletteAsyncListener listener) {
        return from(PixelMap).generate(listener);
    }

    /**
     * @deprecated Use {@link Builder} to generate the Palette.
     */
    @Deprecated
    public static EventRunner generateAsync(PixelMap PixelMap, int numColors, PaletteAsyncListener listener) {
        return from(PixelMap).maximumColorCount(numColors).generate(listener);
    }

    private final List<Swatch> mSwatches;
    private final List<Target> mTargets;

    private final Map<Target, Swatch> mSelectedSwatches;
    private final Map<Integer, Boolean> mUsedColors;

    private final Swatch mDominantSwatch;

    Palette(List<Swatch> swatches, List<Target> targets) {
        mSwatches = swatches;
        mTargets = targets;

        mUsedColors = new HashMap<>();
        mSelectedSwatches = new HashMap<>();

        mDominantSwatch = findDominantSwatch();
    }

    /**
     * Returns all of the swatches which make up the palette.
     */
    
    public List<Swatch> getSwatches() {
        return Collections.unmodifiableList(mSwatches);
    }

    /**
     * Returns the targets used to generate this palette.
     */
    
    public List<Target> getTargets() {
        return Collections.unmodifiableList(mTargets);
    }

    /**
     * Returns the most vibrant swatch in the palette. Might be null.
     *
     * @see Target#VIBRANT
     */
    
    public Swatch getVibrantSwatch() {
        return getSwatchForTarget(Target.VIBRANT);
    }

    /**
     * Returns a light and vibrant swatch from the palette. Might be null.
     *
     * @see Target#LIGHT_VIBRANT
     */
    
    public Swatch getLightVibrantSwatch() {
        return getSwatchForTarget(Target.LIGHT_VIBRANT);
    }

    /**
     * Returns a dark and vibrant swatch from the palette. Might be null.
     *
     * @see Target#DARK_VIBRANT
     */
    
    public Swatch getDarkVibrantSwatch() {
        return getSwatchForTarget(Target.DARK_VIBRANT);
    }

    /**
     * Returns a muted swatch from the palette. Might be null.
     *
     * @see Target#MUTED
     */
    
    public Swatch getMutedSwatch() {
        return getSwatchForTarget(Target.MUTED);
    }

    /**
     * Returns a muted and light swatch from the palette. Might be null.
     *
     * @see Target#LIGHT_MUTED
     */
    
    public Swatch getLightMutedSwatch() {
        return getSwatchForTarget(Target.LIGHT_MUTED);
    }

    /**
     * Returns a muted and dark swatch from the palette. Might be null.
     *
     * @see Target#DARK_MUTED
     */
    
    public Swatch getDarkMutedSwatch() {
        return getSwatchForTarget(Target.DARK_MUTED);
    }

    /**
     * Returns the most vibrant color in the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getVibrantSwatch()
     */
    
    public int getVibrantColor(final int defaultColor) {
        return getColorForTarget(Target.VIBRANT, defaultColor);
    }

    /**
     * Returns a light and vibrant color from the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getLightVibrantSwatch()
     */
    
    public int getLightVibrantColor(final int defaultColor) {
        return getColorForTarget(Target.LIGHT_VIBRANT, defaultColor);
    }

    /**
     * Returns a dark and vibrant color from the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getDarkVibrantSwatch()
     */
    
    public int getDarkVibrantColor(final int defaultColor) {
        return getColorForTarget(Target.DARK_VIBRANT, defaultColor);
    }

    /**
     * Returns a muted color from the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getMutedSwatch()
     */
    
    public int getMutedColor(final int defaultColor) {
        return getColorForTarget(Target.MUTED, defaultColor);
    }

    /**
     * Returns a muted and light color from the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getLightMutedSwatch()
     */
    
    public int getLightMutedColor(final int defaultColor) {
        return getColorForTarget(Target.LIGHT_MUTED, defaultColor);
    }

    /**
     * Returns a muted and dark color from the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getDarkMutedSwatch()
     */
    
    public int getDarkMutedColor(final int defaultColor) {
        return getColorForTarget(Target.DARK_MUTED, defaultColor);
    }

    /**
     * Returns the selected swatch for the given target from the palette, or {@code null} if one
     * could not be found.
     */
    
    public Swatch getSwatchForTarget( final Target target) {
        return mSelectedSwatches.get(target);
    }

    /**
     * Returns the selected color for the given target from the palette as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     */
    
    public int getColorForTarget( final Target target, final int defaultColor) {
        Swatch swatch = getSwatchForTarget(target);
        return swatch != null ? swatch.getRgb() : defaultColor;
    }

    /**
     * Returns the dominant swatch from the palette.
     *
     * <p>The dominant swatch is defined as the swatch with the greatest population (frequency)
     * within the palette.</p>
     */
    
    public Swatch getDominantSwatch() {
        return mDominantSwatch;
    }

    /**
     * Returns the color of the dominant swatch from the palette, as an RGB packed int.
     *
     * @param defaultColor value to return if the swatch isn't available
     * @see #getDominantSwatch()
     */
    
    public int getDominantColor(int defaultColor) {
        return mDominantSwatch != null ? mDominantSwatch.getRgb() : defaultColor;
    }

    void generate() {
        // We need to make sure that the scored targets are generated first. This is so that
        // inherited targets have something to inherit from
        for (int i = 0, count = mTargets.size(); i < count; i++) {
            final Target target = mTargets.get(i);
            target.normalizeWeights();
            Swatch swatch = generateScoredTarget(target);
            LogUtil.i("mSelectedSwatches swatch: "+JsonUtil.toJson(swatch));
            mSelectedSwatches.put(target, swatch);
        }
        // We now clear out the used colors
        mUsedColors.clear();
    }

    
    private Swatch generateScoredTarget(final Target target) {
        final Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target);
        if (maxScoreSwatch != null && target.isExclusive()) {
            // If we have a swatch, and the target is exclusive, add the color to the used list
            mUsedColors.put(maxScoreSwatch.getRgb(), true);
        }
        return maxScoreSwatch;
    }

    
    private Swatch getMaxScoredSwatchForTarget(final Target target) {
        float maxScore = 0;
        Swatch maxScoreSwatch = null;
        for (int i = 0, count = mSwatches.size(); i < count; i++) {
            final Swatch swatch = mSwatches.get(i);
            if (shouldBeScoredForTarget(swatch, target)) {
                final float score = generateScore(swatch, target);
                if (maxScoreSwatch == null || score > maxScore) {
                    maxScoreSwatch = swatch;
                    maxScore = score;
                }
            }
        }
        return maxScoreSwatch;
    }

    private boolean shouldBeScoredForTarget(final Swatch swatch, final Target target) {
        // Check whether the HSL values are within the correct ranges, and this color hasn't
        // been used yet.
        final float hsl[] = swatch.getHsl();
        return hsl[1] >= target.getMinimumSaturation() && hsl[1] <= target.getMaximumSaturation()
                && hsl[2] >= target.getMinimumLightness() && hsl[2] <= target.getMaximumLightness()
                && !(mUsedColors.get(swatch.getRgb()) != null && mUsedColors.get(swatch.getRgb()));
    }

    private float generateScore(Swatch swatch, Target target) {
        final float[] hsl = swatch.getHsl();

        float saturationScore = 0;
        float luminanceScore = 0;
        float populationScore = 0;

        final int maxPopulation = mDominantSwatch != null ? mDominantSwatch.getPopulation() : 1;

        if (target.getSaturationWeight() > 0) {
            saturationScore = target.getSaturationWeight()
                    * (1f - Math.abs(hsl[1] - target.getTargetSaturation()));
        }
        if (target.getLightnessWeight() > 0) {
            luminanceScore = target.getLightnessWeight()
                    * (1f - Math.abs(hsl[2] - target.getTargetLightness()));
        }
        if (target.getPopulationWeight() > 0) {
            populationScore = target.getPopulationWeight()
                    * (swatch.getPopulation() / (float) maxPopulation);
        }

        return saturationScore + luminanceScore + populationScore;
    }

    
    private Swatch findDominantSwatch() {
        int maxPop = Integer.MIN_VALUE;
        Swatch maxSwatch = null;
        for (int i = 0, count = mSwatches.size(); i < count; i++) {
            Swatch swatch = mSwatches.get(i);
            if (swatch.getPopulation() > maxPop) {
                maxSwatch = swatch;
                maxPop = swatch.getPopulation();
            }
        }
        return maxSwatch;
    }

    /**
     * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
     * by calling {@link #getRgb()}.
     */
    public static final class Swatch {
        private final int mRed, mGreen, mBlue;
        private final int mRgb;
        private final int mPopulation;

        private boolean mGeneratedTextColors;
        private int mTitleTextColor;
        private int mBodyTextColor;

         private float[] mHsl;

        public Swatch(int color, int population) {
            mRed = ColorUtil.red(color);
            mGreen = ColorUtil.green(color);
            mBlue = ColorUtil.blue(color);
            mRgb = color;
            mPopulation = population;
        }

        Swatch(int red, int green, int blue, int population) {
            mRed = red;
            mGreen = green;
            mBlue = blue;
            mRgb = ColorUtil.rgb(red, green, blue);
            mPopulation = population;
        }

        Swatch(float[] hsl, int population) {
            this(ColorUtil.HSL2Color(hsl), population);
            mHsl = hsl;
        }

        /**
         * @return this swatch's RGB color value
         */
        
        public int getRgb() {
            return mRgb;
        }

        /**
         * Return this swatch's HSL values.
         *     hsv[0] is Hue [0 .. 360)
         *     hsv[1] is Saturation [0...1]
         *     hsv[2] is Lightness [0...1]
         */
        
        public float[] getHsl() {
            if (mHsl == null) {
                mHsl = new float[3];
            }
            ColorUtil.RGB2HSL(mRed, mGreen, mBlue, mHsl);
            return mHsl;
        }

        /**
         * @return the number of pixels represented by this swatch
         */
        public int getPopulation() {
            return mPopulation;
        }

        /**
         * Returns an appropriate color to use for any 'title' text which is displayed over this
         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
         */
        
        public int getTitleTextColor() {
            ensureTextColorsGenerated();
            return mTitleTextColor;
        }

        /**
         * Returns an appropriate color to use for any 'body' text which is displayed over this
         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
         */
        
        public int getBodyTextColor() {
            ensureTextColorsGenerated();
            return mBodyTextColor;
        }

        private void ensureTextColorsGenerated() {
            if (!mGeneratedTextColors) {
                // First check white, as most colors will be dark
                final int lightBodyAlpha = ColorUtil.calculateMinimumAlpha(
                        COLOR_WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
                final int lightTitleAlpha = ColorUtil.calculateMinimumAlpha(
                        COLOR_WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);
                if (lightBodyAlpha != -1 && lightTitleAlpha != -1) {
                    // If we found valid light values, use them and return
                    mBodyTextColor = ColorUtil.setAlphaComponent(COLOR_WHITE, lightBodyAlpha);
                    mTitleTextColor = ColorUtil.setAlphaComponent(COLOR_WHITE, lightTitleAlpha);
                    mGeneratedTextColors = true;
                    return;
                }

                final int darkBodyAlpha = ColorUtil.calculateMinimumAlpha(
                        COLOR_BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
                final int darkTitleAlpha = ColorUtil.calculateMinimumAlpha(
                        COLOR_BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);

                if (darkBodyAlpha != -1 && darkTitleAlpha != -1) {
                    // If we found valid dark values, use them and return
                    mBodyTextColor = ColorUtil.setAlphaComponent(COLOR_BLACK, darkBodyAlpha);
                    mTitleTextColor = ColorUtil.setAlphaComponent(COLOR_BLACK, darkTitleAlpha);
                    mGeneratedTextColors = true;
                    return;
                }

                // If we reach here then we can not find title and body values which use the same
                // lightness, we need to use mismatched values
                mBodyTextColor = lightBodyAlpha != -1
                        ? ColorUtil.setAlphaComponent(COLOR_WHITE, lightBodyAlpha)
                        : ColorUtil.setAlphaComponent(COLOR_BLACK, darkBodyAlpha);
                mTitleTextColor = lightTitleAlpha != -1
                        ? ColorUtil.setAlphaComponent(COLOR_WHITE, lightTitleAlpha)
                        : ColorUtil.setAlphaComponent(COLOR_BLACK, darkTitleAlpha);
                mGeneratedTextColors = true;
            }
        }

        @Override
        public String toString() {
            return new StringBuilder(getClass().getSimpleName())
                    .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
                    .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
                    .append(" [Population: ").append(mPopulation).append(']')
                    .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor()))
                    .append(']')
                    .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor()))
                    .append(']').toString();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            Swatch swatch = (Swatch) o;
            return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
        }

        @Override
        public int hashCode() {
            return 31 * mRgb + mPopulation;
        }
    }

    /**
     * Builder class for generating {@link Palette} instances.
     */
    public static final class Builder {
         private final List<Swatch> mSwatches;
         private final PixelMap mPixelMap;

        private final List<Target> mTargets = new ArrayList<>();

        private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
        private int mResizeArea = DEFAULT_RESIZE_PixelMap_AREA;
        private int mResizeMaxDimension = -1;

        private final List<Filter> mFilters = new ArrayList<>();
         private Rect mRegion;

        /**
         * Construct a new {@link Builder} using a source {@link PixelMap}
         */
        public Builder( PixelMap pixelMap) {
            if (pixelMap == null || pixelMap.isReleased()) {
                throw new IllegalArgumentException("pixelMap is not valid");
            }
            mFilters.add(DEFAULT_FILTER);
            mPixelMap = pixelMap;
            mSwatches = null;

            // Add the default targets
            mTargets.add(Target.LIGHT_VIBRANT);
            mTargets.add(Target.VIBRANT);
            mTargets.add(Target.DARK_VIBRANT);
            mTargets.add(Target.LIGHT_MUTED);
            mTargets.add(Target.MUTED);
            mTargets.add(Target.DARK_MUTED);
        }

        /**
         * Construct a new {@link Builder} using a list of {@link Swatch} instances.
         * Typically only used for testing.
         */
        public Builder( List<Swatch> swatches) {
            if (swatches == null || swatches.isEmpty()) {
                throw new IllegalArgumentException("List of Swatches is not valid");
            }
            mFilters.add(DEFAULT_FILTER);
            mSwatches = swatches;
            mPixelMap = null;
        }

        /**
         * Set the maximum number of colors to use in the quantization step when using a
         * {@link PixelMap} as the source.
         * <p>
         * Good values for depend on the source image type. For landscapes, good values are in
         * the range 10-16. For images which are largely made up of people's faces then this
         * value should be increased to ~24.
         */
        
        public Builder maximumColorCount(int colors) {
            mMaxColors = colors;
            return this;
        }

        /**
         * Set the resize value when using a {@link PixelMap} as the source.
         * If the PixelMap's largest dimension is greater than the value specified, then the PixelMap
         * will be resized so that its largest dimension matches {@code maxDimension}. If the
         * PixelMap is smaller or equal, the original is used as-is.
         *
         * @deprecated Using {@link #resizePixelMapArea(int)} is preferred since it can handle
         * abnormal aspect ratios more gracefully.
         *
         * @param maxDimension the number of pixels that the max dimension should be scaled down to,
         *                     or any value <= 0 to disable resizing.
         */
        
        @Deprecated
        public Builder resizePixelMapSize(final int maxDimension) {
            mResizeMaxDimension = maxDimension;
            mResizeArea = -1;
            return this;
        }

        /**
         * Set the resize value when using a {@link PixelMap} as the source.
         * If the PixelMap's area is greater than the value specified, then the PixelMap
         * will be resized so that its area matches {@code area}. If the
         * PixelMap is smaller or equal, the original is used as-is.
         * <p>
         * This value has a large effect on the processing time. The larger the resized image is,
         * the greater time it will take to generate the palette. The smaller the image is, the
         * more detail is lost in the resulting image and thus less precision for color selection.
         *
         * @param area the number of pixels that the intermediary scaled down PixelMap should cover,
         *             or any value <= 0 to disable resizing.
         */
        
        public Builder resizePixelMapArea(final int area) {
            mResizeArea = area;
            mResizeMaxDimension = -1;
            return this;
        }

        /**
         * Clear all added filters. This includes any default filters added automatically by
         * {@link Palette}.
         */
        
        public Builder clearFilters() {
            mFilters.clear();
            return this;
        }

        /**
         * Add a filter to be able to have fine grained control over which colors are
         * allowed in the resulting palette.
         *
         * @param filter filter to add.
         */
        
        public Builder addFilter(Filter filter) {
            if (filter != null) {
                mFilters.add(filter);
            }
            return this;
        }

        /**
         * Set a region of the PixelMap to be used exclusively when calculating the palette.
         * <p>This only works when the original input is a {@link PixelMap}.</p>
         *
         * @param left The left side of the rectangle used for the region.
         * @param top The top of the rectangle used for the region.
         * @param right The right side of the rectangle used for the region.
         * @param bottom The bottom of the rectangle used for the region.
         */
        
        public Builder setRegion(int left, int top, int right, int bottom) {
            if (mPixelMap != null) {
                if (mRegion == null) mRegion = new Rect();
                // Set the Rect to be initially the whole PixelMap
                mRegion.set(0, 0, mPixelMap.getImageInfo().size.width, mPixelMap.getImageInfo().size.height);
                // Now just get the intersection with the region
                if (!intersect(mRegion, left, top, right, bottom)) {
                    throw new IllegalArgumentException("The given region must intersect with the PixelMap's dimensions.");
                }
            }
            return this;
        }

        public boolean intersect(Rect rect, int left, int top, int right, int bottom) {
            if (rect.left < right && left < rect.right && rect.top < bottom && top < rect.bottom) {
                if (rect.left < left) rect.left = left;
                if (rect.top < top) rect.top = top;
                if (rect.right > right) rect.right = right;
                if (rect.bottom > bottom) rect.bottom = bottom;
                return true;
            }
            return false;
        }

        /**
         * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
         */
        
        public Builder clearRegion() {
            mRegion = null;
            return this;
        }

        /**
         * Add a target profile to be generated in the palette.
         *
         * <p>You can retrieve the result via {@link Palette#getSwatchForTarget(Target)}.</p>
         */
        
        public Builder addTarget( final Target target) {
            if (!mTargets.contains(target)) {
                mTargets.add(target);
            }
            return this;
        }

        /**
         * Clear all added targets. This includes any default targets added automatically by
         * {@link Palette}.
         */
        
        public Builder clearTargets() {
            mTargets.clear();
            return this;
        }

        /**
         * Generate and return the {@link Palette} synchronously.
         */
        
        public Palette generate(EventHandler handler) {

            List<Swatch> swatches;

            if (mPixelMap != null) {
                // We have a pixelMap so we need to use quantization to reduce the number of colors

                // First we'll scale down the pixelMap if needed
                final PixelMap pixelMap = scalePixelMapDown(mPixelMap);
                if (pixelMap==null)throw new AssertionError();
                final Rect region = mRegion;
                if (pixelMap != mPixelMap && region != null) {
                    // If we have a scaled pixelMap and a selected region, we need to scale down the
                    // region to match the new scale
                    final double scale = pixelMap.getImageInfo().size.width / (double) mPixelMap.getImageInfo().size.width;
                    region.left = (int) Math.floor(region.left * scale);
                    region.top = (int) Math.floor(region.top * scale);
                    region.right = Math.min((int) Math.ceil(region.right * scale), pixelMap.getImageInfo().size.width);
                    region.bottom = Math.min((int) Math.ceil(region.bottom * scale), pixelMap.getImageInfo().size.height);

                }

                // Now generate a quantizer from the pixelMap
                final ColorCutQuantizer quantizer = new ColorCutQuantizer(
                        getPixelsFromPixelMap(pixelMap),
                        mMaxColors,
                        mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()]));
                // If created a new pixelMap, recycle it
                if (pixelMap != mPixelMap) {
                    pixelMap.release();
                }
                swatches = quantizer.getQuantizedColors();
                LogUtil.i("Color quantization completed");
                LogUtil.i("mSwatches is null: %b", mSwatches==null);
            } else if (mSwatches != null) {
                // Else we're using the provided swatches
                swatches = mSwatches;
            } else {
                // The constructors enforce either a PixelMap or swatches are present.
                throw new AssertionError();
            }

            // Now create a Palette instance
            final Palette p = new Palette(swatches, mTargets);
            // And make it generate itself
            p.generate();
            LogUtil.i("Targets: %s", JsonUtil.toJson(mTargets));
            LogUtil.i("Palette: %s", JsonUtil.toJson(p));
            if (handler!=null){
                InnerEvent event = InnerEvent.get();
                event.object = p;
                handler.sendEvent(event);
            }
            LogUtil.i("Created Palette");
            return p;
        }

        /**
         * Generate the {@link Palette} asynchronously. The provided listener's
         * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when
         * generated.
         */
        public EventRunner generate(PaletteAsyncListener listener) {
            if (listener == null) {
                throw new IllegalArgumentException("listener can not be null");
            }
            EventRunner eventRunner = EventRunner.create(true);
            EventHandler handler = new C_Handler(eventRunner, listener);
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    generate(handler);
                }
            });

            return eventRunner;
        }

        private int[] getPixelsFromPixelMap(PixelMap pixelMap) {
            final int PixelMapWidth = pixelMap.getImageInfo().size.width;
            final int PixelMapHeight = pixelMap.getImageInfo().size.height;
            final int[] pixels = new int[PixelMapWidth * PixelMapHeight];
            pixelMap.readPixels(pixels, 0, PixelMapWidth, new ohos.media.image.common.Rect(0, 0, PixelMapWidth, PixelMapHeight));

            if (mRegion == null) {
                // If we don't have a region, return all of the pixels
                return pixels;
            } else {
                // If we do have a region, lets create a subset array containing only the region's
                // pixels
                final int regionWidth = mRegion.getWidth();
                final int regionHeight = mRegion.getHeight();
                // pixels contains all of the pixels, so we need to iterate through each row and
                // copy the regions pixels into a new smaller array
                final int[] subsetPixels = new int[regionWidth * regionHeight];
                for (int row = 0; row < regionHeight; row++) {
                    System.arraycopy(pixels, ((row + mRegion.top) * PixelMapWidth) + mRegion.left,
                            subsetPixels, row * regionWidth, regionWidth);
                }
                return subsetPixels;
            }
        }

        /**
         * Scale the pixelMap down as needed.
         */
        private PixelMap scalePixelMapDown(final PixelMap pixelMap) {
            double scaleRatio = -1;

            if (mResizeArea > 0) {
                final int pixelMapArea = pixelMap.getImageInfo().size.width * pixelMap.getImageInfo().size.height;
                if (pixelMapArea > mResizeArea) {
                    scaleRatio = Math.sqrt(mResizeArea / (double) pixelMapArea);
                }
            } else if (mResizeMaxDimension > 0) {
                final int maxDimension = Math.max(pixelMap.getImageInfo().size.width, pixelMap.getImageInfo().size.height);
                if (maxDimension > mResizeMaxDimension) {
                    scaleRatio = mResizeMaxDimension / (double) maxDimension;
                }
            }
            if (scaleRatio <= 0) {
                // Scaling has been disabled or not needed so just return the pixelMap
                return pixelMap;
            }
            int width = (int) Math.ceil(pixelMap.getImageInfo().size.width * scaleRatio);
            int height = (int) Math.ceil(pixelMap.getImageInfo().size.height * scaleRatio);
            return PixelMapFactory.CreatePixelMap(pixelMap, width, height);
        }
    }

    public static class C_Handler extends EventHandler{

        private PaletteAsyncListener listener;

        public C_Handler(EventRunner runner, PaletteAsyncListener listener) throws IllegalArgumentException {
            super(runner);
            this.listener = listener;
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            if (listener!=null)listener.onGenerated((Palette) event.object);
        }
    }

    /**
     * A Filter provides a mechanism for exercising fine-grained control over which colors
     * are valid within a resulting {@link Palette}.
     */
    public interface Filter {
        /**
         * Hook to allow clients to be able filter colors from resulting palette.
         *
         * @param rgb the color in RGB888.
         * @param hsl HSL representation of the color.
         *
         * @return true if the color is allowed, false if not.
         *
         * @see Builder#addFilter(Filter)
         */
        boolean isAllowed(int rgb,  float[] hsl);
    }

    /**
     * The default filter.
     */
    static final Filter DEFAULT_FILTER = new Filter() {
        private static final float BLACK_MAX_LIGHTNESS = 0.05f;
        private static final float WHITE_MIN_LIGHTNESS = 0.95f;

        @Override
        public boolean isAllowed(int rgb, float[] hsl) {
            return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl);
        }

        /**
         * @return true if the color represents a color which is close to black.
         */
        private boolean isBlack(float[] hslColor) {
            return hslColor[2] <= BLACK_MAX_LIGHTNESS;
        }

        /**
         * @return true if the color represents a color which is close to white.
         */
        private boolean isWhite(float[] hslColor) {
            return hslColor[2] >= WHITE_MIN_LIGHTNESS;
        }

        /**
         * @return true if the color lies close to the red side of the I line.
         */
        private boolean isNearRedILine(float[] hslColor) {
            return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
        }
    };
}
